/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2010.

	This file is part of Maxsi Engine.

	Maxsi Engine is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi Engine is distributed in the hope that it will be useful, but WITHOUT
	ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
	FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
	for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	MaxsiEngine.dll
	A general purpose C++ library for the Maxsi Technology Project.

	MaxsiTranslate.cpp
	A system for loading string table files and storing them in memory, so
	that when any parts of the application needs to be localized, strings can
	be easily localized using the Translate() function.

******************************************************************************/

#include "MaxsiEngine.h"

BeginMaxsiNamespace

MaxsiTranslateLanguage** StringTables	=	NULL;
size_t  NumStringTables					=	0;
MESTR*	CurrentLanguage					=	NULL;
MESTR*	LanguageErrors					=	NULL;
MESTR*	LoadError						=	NULL;

MESTR* GetTranslationDir()
{
	return _MESTR("../Translations/");
}

LINK MaxsiError LoadMaxsiComponentConfiguration(MESTR* ComponentName)
{
#ifdef Maxsi_Support_W64
	return MAXSI_ERROR_SUCCESS;
#endif

	srand ( time(NULL) );

	MaxsiError	Result;
	size_t		CurrentLanguageLen;

	if ( CurrentLanguage != NULL ) { delete[] CurrentLanguage; CurrentLanguage = NULL; }

	// Load the language file!
	if ( (Result = ReadTextFile(_MESTR("../Language.hack"), &CurrentLanguage, &CurrentLanguageLen, MAXSI_STR_UTF_8)) != MAXSI_ERROR_SUCCESS )
	{
		CurrentLanguage		=	BuildString(1, _MESTR("English"));
	}

	if ( CurrentLanguage == NULL ) { PrintOutput("%MS", Translate("MaxsiEngine/MaxsiTranslateErrorSettingLanguage")); return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	// Ensure the name of the language is just one word!
	size_t FirstWhiteSpace = NextWhiteSpace(CurrentLanguage, CurrentLanguageLen);

	if ( FirstWhiteSpace != SIZE_MAX ) { CurrentLanguage[FirstWhiteSpace] = 0; }	

	PrintOutput("Changed language to %MS.\n", CurrentLanguage);

	MAXSI_TODO("This is hard-coded stuff that must be moved to configuration files!");

	#define NUM_STRING_TABLES 3

	MESTR*	StringTables[NUM_STRING_TABLES] = { _MESTR("MaxsiEngine"), _MESTR("MaxsiDistribution"), _MESTR("MaxsiInstaller") };

	for ( size_t I = 0; I < NUM_STRING_TABLES; I++ )
	{
		MaxsiError	LoadResult	=	LoadLanguageFile(CurrentLanguage, StringTables[I]);

		if ( LoadResult != MAXSI_ERROR_SUCCESS )
		{
			MESTR* TempLanguageErrors = (LanguageErrors) ? LanguageErrors : (MESTR*)_MESTR("");					

			MESTR* NewError		=	BuildString(10, TempLanguageErrors, _MESTR("\n"), MaxsiErrorName(LoadResult), _MESTR(" "), Translate("MaxsiEngine/MaxsiTranslateErrorWhileReadingLanguageFile"), _MESTR(" ../Translations/"), StringTables[I], _MESTR("_"), CurrentLanguage, _MESTR(".txt"));

			//MESTR* NewError		=	PrintString("%MS\n%MS %MS ../Translations/%MS_%MS.txt", TempLanguageErrors, MaxsiErrorName(LoadResult), Translate("MaxsiEngine/MaxsiTranslateErrorWhileReadingLanguageFile"), StringTables[I], CurrentLanguage);

			if ( NewError != NULL ) { PrintOutput("%MS\n", NewError); IFDEL(LanguageErrors); LanguageErrors = NewError; }

			Result	=	LoadResult;
		}
	}

	if ( LoadError ) { delete[] LoadError; }

	LoadError	=	BuildString(2, Translate("MaxsiEngine/MaxsiTranslateErrorLoadingConfigurationHeader"), LanguageErrors);

	return Result;
}

LINK MaxsiError UnloadMaxsiComponentsConfigurations()
{
	delete[] CurrentLanguage;

	return MAXSI_ERROR_SUCCESS;
}

MESTR* BuiltInString(MESTR* StringName)
{
	if ( MESTRICMP(StringName, _MESTR("MaxsiEngine/MaxsiTranslateErrorHeader")) == 0 )
	{ return _MESTR("Error in Maxsi Translate"); } else

	if ( MESTRICMP(StringName, _MESTR("MaxsiEngine/MaxsiTranslateErrorLoadingConfiguration")) == 0 )
	{ if (LoadError) { return LoadError; } else { return Translate("BuiltIn/MaxsiTranslateErrorLoadingConfigurationStatic"); } } else

	if ( MESTRICMP(StringName, _MESTR("MaxsiEngine/MaxsiTranslateErrorLoadingConfigurationHeader")) == 0 )
	{ return _MESTR("Maxsi Translate experienced issues while attempting to load configuration and language strings.\n\nSome text in the User Interface might not be displayed correctly in your language or at all.\n\nThis might be caused by missing or invalid language files in ../Translate/ or a missing or invalid ../Build.ini.\n\nThe exact errors are:"); } else

	if ( MESTRICMP(StringName, _MESTR("MaxsiEngine/MaxsiTranslateErrorLoadingConfigurationStatic")) == 0 )
	{ return _MESTR("Maxsi Translate could not load configuration and language strings. The User Interface might not be displayed in any language.\n\nThe build configuration file ../Build.ini is either missing or broken, or a required string table for your language in ../Translate/ is missing or broken.\n\nIn addition it was not possible to allocate enough memory to store the exact error message and display it here.\n\nThe program will continue attempting to launch, but its User Interface and other features might not work correctly."); } else

	if ( MESTRICMP(StringName, _MESTR("MaxsiEngine/MaxsiTranslateErrorSettingLanguage")) == 0 )
	{ return _MESTR("Maxsi Translate: Could not set current language!\n"); } else

	if ( MESTRICMP(StringName, _MESTR("MaxsiEngine/MaxsiTranslateErrorWhileReadingLanguageFile")) == 0 )
	{ return _MESTR("occured while loading language file"); } else

	if ( MESTRICMP(StringName, _MESTR("MaxsiEngine/MaxsiTranslateWasPassedInvalidString")) == 0 )
	{ return _MESTR("#Invalid String Name used in Translate()#"); }

	return StringName;
}

MESTR* LookUpTranslation(MESTR* StringName)
{
	//return _MESTR("Maxsi Translate has been disabled.");

	// Check if a built in string was requested. They are static strings used in cases where Maxsi Translate failed horribly.
	if ( MESTRNICMP(StringName, _MESTR("BuiltIn/"), MESTRLEN(_MESTR("BuiltIn/"))) == 0 )
	{
		return BuiltInString(StringName);
	}

	size_t	Seperator		=	FindChar(StringName, '/', MESTRLEN(StringName));

	if ( Seperator == SIZE_MAX ) { return Translate("MaxsiEngine/MaxsiTranslateWasPassedInvalidString"); }

	size_t	StringTable		=	SIZE_MAX;

	// Find the requested string table!	
	for ( size_t I = 0; I < NumStringTables; I++ )
	{
		// See if we found the requested string table!
		if ( StringTables[I] && MESTRICMP(CurrentLanguage, StringTables[I]->Language) == 0 && MESTRNICMP(StringName, StringTables[I]->StringTableName, Seperator) == 0 )
		{
			StringTable		=	I;
		}
	}

	if ( StringTable != SIZE_MAX )
	{
		StringName	+=	Seperator + 1;

		MAXSI_TODO_LOW("Use a binary search algorithm here using a sorted array, which will improve performance hugely for a lot of strings.");

		size_t NumStrings = StringTables[StringTable]->NumStrings * 2;

		// Look up the requested string!
		for ( size_t I = 0; I < NumStrings; I += 2 )
		{
			// Have we found the one?
			if ( MESTRICMP(StringName, StringTables[StringTable]->StringTable[I]) == 0 )
			{
				return StringTables[StringTable]->StringTable[I+1];
			}
		}

		StringName = StringName - Seperator - 1;
	}

	// Do a last desperate attempt to retrieve the string from a small pool of hardcoded strings meant for a case of emergency!
	return BuiltInString(StringName);
}

size_t AddStringTable(MESTR* Language, MESTR* StringTableName)
{
	//PrintOutput("Adding string table %MS_%MS.txt!\n", StringTableName, Language);

	for ( size_t I = 0; I < NumStringTables; I++ )
	{
		// See if the string table is already added!
		if ( StringTables[I] && MESTRICMP(Language, StringTables[I]->Language) == 0 && MESTRICMP(StringTableName, StringTables[I]->StringTableName) == 0 )
		{
			// Delete all the strings!
			for ( size_t N = 0; N < StringTables[I]->NumStrings*2; N++ ) { delete[] StringTables[I]->StringTable[N]; }

			// Delete the string table list!
			delete[] StringTables[I]->StringTable;

			// Reset the pointers!
			StringTables[I]->StringTable	=	NULL;
			StringTables[I]->NumStrings		=	0;

			return I;
		}
	}

	MaxsiTranslateLanguage** NewStringTables	=	new MaxsiTranslateLanguage*[NumStringTables+1];

	if ( NewStringTables == NULL ) { return SIZE_MAX; } // MAXSI_ERROR_MEM_ALLOC_FAIL

	if ( StringTables && NumStringTables )
	{
		memcpy(NewStringTables, StringTables, sizeof(MaxsiTranslateLanguage*) * NumStringTables);
	}

	// Allocate the actual new object!
	NewStringTables[NumStringTables]					=	new MaxsiTranslateLanguage;

	if ( NewStringTables[NumStringTables] == NULL ) { delete[] NewStringTables; return SIZE_MAX; } // MAXSI_ERROR_MEM_ALLOC_FAIL

	NewStringTables[NumStringTables]->Language			=	BuildString(1, Language);

	if ( NewStringTables[NumStringTables]->Language == NULL ) { delete NewStringTables[NumStringTables]; delete[] NewStringTables; return SIZE_MAX; } // MAXSI_ERROR_MEM_ALLOC_FAIL

	NewStringTables[NumStringTables]->StringTableName	=	BuildString(1, StringTableName);

	if ( NewStringTables[NumStringTables]->StringTableName == NULL ) { delete NewStringTables[NumStringTables]; delete[] NewStringTables[NumStringTables]->Language; delete[] NewStringTables; return SIZE_MAX; } // MAXSI_ERROR_MEM_ALLOC_FAIL

	NewStringTables[NumStringTables]->StringTable		=	NULL;
	NewStringTables[NumStringTables]->NumStrings		=	0;

	delete[] StringTables;

	StringTables	=	NewStringTables;
	NumStringTables++;

	return (NumStringTables-1);
}

MaxsiError LoadLanguageFile(MESTR* Language, MESTR* StringTableName)
{
	//return rand() % MAXSI_NUM_ERRORS;

	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	// Build the filename!
	MESTR* FileName = BuildString(5, GetTranslationDir(), StringTableName, _MESTR("_"), Language, _MESTR(".txt"));

	// Check for error conditions.
	if ( FileName == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }
	
	// Declare the destination buffers!
	MESTR*	Data		=	NULL;
	size_t	DataLength	=	0;

	PrintOutput("Loading language file %MS.\n", FileName);

	// Load the language file!
	if ( (Result = ReadTextFile(FileName, &Data, &DataLength, MAXSI_STR_UTF_8)) == MAXSI_ERROR_SUCCESS )
	{
		if ( Data != NULL && DataLength > 0 )
		{
			// Load the buffer into memory!
			Result = ParseLanguageFile(Language, StringTableName, Data, DataLength);
		}

		IFDEL(Data);
	}
	else
	{
		PrintOutput("Error %ME loading language file %MS\n", Result, FileName);
	}
	
	return Result;
}

MaxsiError ParseLanguageFile(MESTR* Language, MESTR* StringTableName, MESTR* Data, size_t DataLength, size_t* NumStrings)
{
	//PrintOutput("Parsing file %MS_%MS.txt:\n%MS\n\n", StringTableName, Language, Data);

	MaxsiError Result		=	MAXSI_ERROR_SUCCESS;
	
	if ( NumStrings == NULL )
	{
		size_t	HowManyStrings	=	0;

		// Measure how many strings are required by calling ourselves without a valid language name!
		Result	=	ParseLanguageFile(NULL, NULL, Data, DataLength, &HowManyStrings );

		if ( Result != MAXSI_ERROR_SUCCESS ) { return Result; }

		// Do the actual parsing!
		return ParseLanguageFile(Language, StringTableName, Data, DataLength, &HowManyStrings );		
	}

	size_t	StringTableId		=	(Language && StringTableName) ? AddStringTable(Language, StringTableName) : SIZE_MAX;
	size_t	Offset				=	0;
	size_t	StringName			=	0;
	size_t	StringNameEnd		=	0;
	size_t	StringContents		=	0;
	size_t	StringContentsEnd	=	0;

	// If we are parsing strings, simply allocate enough room for them all!
	if ( StringTableId != SIZE_MAX )
	{
		StringTables[StringTableId]->NumStrings		=	*NumStrings;
		StringTables[StringTableId]->StringTable	=	new MESTR*[(*NumStrings) * 2];

		if ( StringTables[StringTableId]->StringTable == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }	

		memset(StringTables[StringTableId]->StringTable, 0, sizeof(MESTR*) * (*NumStrings) * 2);	
	}

	*NumStrings		=	0;

	// Parse our data buffer!
	for ( size_t I = 0; I < DataLength; I++ )
	{
		// Skip any white space!
		Offset	=	SkipWhiteSpace(Data + I, DataLength - I);

		// Check if the rest of the data is whitespace!
		if ( Offset == SIZE_MAX ) { break; }

		// Forward the pointer!
		I += Offset;

		// Remember where we found the beginning of the name of the string!
		StringName	=	I;

		// Fast-forward to the next whitespace!
		Offset	=	NextNonQuotedWhiteSpace(Data + I, DataLength - I);

		// Check if we ever found whitespace again!
		if ( Offset == SIZE_MAX ) { break; }

		// Forward the pointer!
		I += Offset;

		// Remember the where the string name ends!
		StringNameEnd	=	I;

		// Skip any white space!
		Offset	=	SkipWhiteSpace(Data + I, DataLength - I);

		// Check if the rest of the data is whitespace!
		if ( Offset == SIZE_MAX ) { break; }

		// Forward the pointer!
		I += Offset;

		// Remember where we found the actual name of the string!
		StringContents	=	I;	

		// Fast-forward to the next whitespace!
		Offset	=	NextNonQuotedWhiteSpace(Data + I, DataLength - I);

		// Check if we ever found whitespace again!
		if ( Offset == SIZE_MAX ) { Offset = DataLength - I; }

		// Forward the pointer!
		I += Offset;

		// Copy the strings, if we are parsing!
		if ( StringTableId != SIZE_MAX )
		{
			// It is not a critical error if these memory allocations fail, as we can always just replace the string with a notice that said this happened.
			StringTables[StringTableId]->StringTable[(*NumStrings) * 2 + 0] = EvaluateQuote(Data + StringName, StringNameEnd - StringName);
			StringTables[StringTableId]->StringTable[(*NumStrings) * 2 + 1] = EvaluateQuote(Data + StringContents, StringContentsEnd - StringContents);

			//PrintOutput("%MS: %MS\n", StringTables[StringTableId]->StringTable[(*NumStrings) * 2 + 0], StringTables[StringTableId]->StringTable[(*NumStrings) * 2 + 1]);
		}

		// Increase the amount of strings we found,
		(*NumStrings)++;
	}

	return Result;
}

EndMaxsiNamespace

